"""
参数管理系统 Python 客户端
支持类似pandas DataFrame的点号访问方式
"""
import requests
import json
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
from urllib.parse import urljoin
import logging

from .exceptions import ParameterClientError, ParameterNotFoundError, CategoryNotFoundError, ProjectNotFoundError, ConnectionError
from .models import ParameterValue, ParameterCategory, Project
from .experiment_collector import ExperimentCollector
from .backend.io_v2.services.importer import IMPORT_STRATEGIES

# 配置日志
logger = logging.getLogger(__name__)


class ParameterClient:
    """
    参数管理系统客户端
    支持类似pandas DataFrame的点号访问方式
    """
    
    def __init__(self, host: str = "localhost", port: int = 8000, project_name: str = None, *, local_file: Optional[str] = None):
        """
        初始化参数客户端
        
        Args:
            host: 服务器地址，可以是IP地址或域名
            port: 服务器端口，默认8000
            project_name: 项目英文名称，如果提供则在初始化时自动加载参数
            local_file: 本地文件路径。提供后优先从本地导入，格式根据文件后缀自动判断
        """
        self.host = host
        self.port = port
        self.project_name = project_name
        self.base_url = f"http://{host}:{port}/api"
        self.session = requests.Session()
        
        # 设置请求头
        self.session.headers.update({
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        })
        
        # 存储项目对象
        self.project = None
        
        # 存储快照数据：如果是本地文件导入，存储文件路径；如果是服务器加载，存储JSON数据
        self.snapshot_data = None  # 如果是服务器加载，存储JSON字典
        
        # 如果提供了本地文件，则优先从本地导入
        if local_file:
            self.load_project_from_file(local_file)
        else:
            # 如果提供了项目名称，自动从服务器加载参数
            if project_name:
                self.load_project(project_name)

    # ==================== 本地导入能力 ====================
    def supported_local_import_formats(self) -> List[str]:
        """返回客户端可用的本地导入格式（使用新版 io_v2 策略系统）。"""
        try:
            formats = []
            for strategy_cls in IMPORT_STRATEGIES.values():
                format_info = strategy_cls.get_format_info()
                formats.append(format_info.get("format"))
            
            return formats
        except Exception as e:
            logger.error(f"获取本地导入格式失败: {e}")
            return []

    def new_experiment(self, name: Optional[str] = None, description: Optional[str] = None) -> ExperimentCollector:
        """
        创建新的实验收集器
        
        Args:
            name: 实验名称（可选，默认自动生成）
            description: 实验描述（可选）
            
        Returns:
            ExperimentCollector实例
        """
        if not self.project:
            raise ValueError("项目未加载，请先调用 load_project()")
        
        return ExperimentCollector(self, name=name, description=description)
    
    def load_project_from_file(self, input_path: str) -> "Project":
        """
        从本地文件导入项目数据并在客户端内存中构建 Project 对象。
        格式根据文件后缀自动判断。

        Args:
            input_path: 本地文件路径

        Returns:
            Project 对象
        """
        path = Path(input_path).expanduser()
        if not path.exists():
            raise FileNotFoundError(f"文件不存在: {input_path}")

        # 根据文件后缀自动判断格式
        file_ext = path.suffix.lower()
        if not file_ext:
            raise ValueError(f"文件没有扩展名，无法判断格式: {input_path}")
        
        strategy_cls = None
        for strategy_class in IMPORT_STRATEGIES.values():
            format_info = strategy_class.get_format_info()
            extensions = format_info.get("extensions", [])
            # 检查文件后缀是否在策略支持的扩展名列表中
            if file_ext in [ext.lower() for ext in extensions]:
                strategy_cls = strategy_class
                break
        
        if not strategy_cls:
            # 收集所有支持的扩展名用于错误提示
            all_extensions = []
            for strategy_class in IMPORT_STRATEGIES.values():
                format_info = strategy_class.get_format_info()
                all_extensions.extend(format_info.get("extensions", []))
            raise ValueError(
                f"不支持的文件格式: {file_ext}, "
                f"支持的扩展名: {', '.join(sorted(set(all_extensions)))}"
            )

        strategy = strategy_cls()
        snapshot = strategy.import_snapshot(path)

        project_info, categories_data = self._build_project_from_snapshot(snapshot)
        snapshot_payload = self._build_snapshot_payload(snapshot)

        self.project = Project(project_info, categories_data)
        self.project_name = project_info["name_en"]
        self.project._client = self

        self.snapshot_data = snapshot_payload
        self.snapshot_file_path = None

        logger.info(f"本地导入项目成功: {self.project_name}")
        return self.project
    
    def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
        """
        发送HTTP请求
        
        Args:
            method: HTTP方法 (GET, POST, PUT, DELETE)
            endpoint: API端点
            **kwargs: 请求参数
            
        Returns:
            响应数据字典
            
        Raises:
            requests.RequestException: 请求失败时抛出
        """
        # 确保endpoint不以/开头，避免urljoin替换掉base_url的路径
        if endpoint.startswith('/'):
            endpoint = endpoint[1:]
        url = urljoin(self.base_url + '/', endpoint)
        
        try:
            logger.info(f"发送请求: {method} {url}")
            response = self.session.request(method, url, **kwargs)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.ConnectionError as e:
            logger.error(f"连接失败: {method} {url}, 错误: {str(e)}")
            hint = (
                f"无法连接到服务器 {self.host}:{self.port}。\n"
                "请检查：\n"
                "- 服务器是否已启动并正常运行\n"
                "- 地址与端口是否填写正确\n"
                "- 当前网络是否可用/有代理拦截\n"
                "若无需远程服务器，可在本地使用内置后端：run_embedded_server(host, port)"
            )
            raise ConnectionError(hint) from e
        except requests.exceptions.HTTPError as e:
            status = getattr(e.response, 'status_code', None)
            reason = getattr(e.response, 'reason', '')
            text = ''
            try:
                text = e.response.text[:200] if e.response is not None else ''
            except Exception:
                text = ''
            if status == 404:
                raise ProjectNotFoundError("未找到资源：请确认项目英文名是否正确，或先在服务器创建该项目。") from e
            if isinstance(status, int) and 500 <= status < 600:
                msg = (
                    f"服务器异常({status} {reason})：{url}\n"
                    "请确认服务器已正常启动并健康运行，可尝试重启服务或稍后重试。"
                )
                logger.error(msg)
                raise ConnectionError(msg) from e
            msg = f"请求失败({status} {reason})：{url}"
            if text:
                msg += f" - {text}"
            msg += "\n如问题持续，请检查网络/服务状态，或联系管理员。"
            logger.error(msg)
            raise ParameterClientError(msg) from e
        except requests.exceptions.RequestException as e:
            logger.error(f"请求失败: {method} {url}, 错误: {str(e)}")
            raise ParameterClientError(f"请求失败: {e}") from e
    
    def load_project(self, project_name: str = None) -> Project:
        """
        加载项目数据（自动保存参数快照）
        
        Args:
            project_name: 项目英文名称，如果为None则使用初始化时的项目名称
            
        Returns:
            Project对象
        """
        project_name = project_name or self.project_name
        if not project_name:
            raise ValueError("必须提供项目名称")
        
        try:
            logger.info(f"正在加载项目详细数据: {project_name}")
            
            # 使用导出接口获取 JSON 格式的项目数据
            response = self._make_request('POST', f'/projects/{project_name}/export/json')
            
            # 使用 JSON 策略解析数据
            from .backend.io_v2.strategies.json_strategy import JsonImportStrategy
            import tempfile
            
            # 将 JSON 数据写入临时文件，然后使用策略解析
            with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as tmp_file:
                json.dump(response, tmp_file, ensure_ascii=False, indent=2)
                tmp_path = Path(tmp_file.name)
            
            json_strategy = JsonImportStrategy()
            
            try:
                snapshot = json_strategy.import_snapshot(tmp_path)
                
                # 构建项目对象
                project_info, categories_data = self._build_project_from_snapshot(snapshot)
                snapshot_payload = self._build_snapshot_payload(snapshot)
                
                self.project = Project(project_info, categories_data)
                self.project_name = project_name
                self.project._client = self
                
                # 保存快照数据
                self.snapshot_data = snapshot_payload
                self.snapshot_file_path = None
            finally:
                # 清理临时文件
                try:
                    tmp_path.unlink()
                except Exception:
                    pass
            
            logger.info(f"成功加载项目: {project_name}")
            logger.info(f"项目分类: {self.project.categories}")
            
            return self.project
            
        except Exception as e:
            logger.error(f"加载项目失败: {str(e)}")
            raise
    
    def get_project(self) -> Project:
        """
        获取项目对象
        
        Returns:
            Project对象
            
        Raises:
            ValueError: 项目未加载时抛出
        """
        if not self.project:
            raise ValueError("项目未加载，请先调用 load_project()")
        return self.project
    
    def refresh_project(self) -> Project:
        """
        刷新项目数据（重新从服务器加载）
        
        Returns:
            更新后的Project对象
        """
        if not self.project_name:
            raise ValueError("未设置项目名称，无法刷新项目")
        
        logger.info("正在刷新项目数据...")
        return self.load_project()
    
    def __str__(self) -> str:
        """返回客户端状态字符串"""
        status = f"ParameterClient(host={self.host}, port={self.port}, project={self.project_name})"
        if self.project:
            status += f" - 已加载项目 '{self.project.name_en}'"
        else:
            status += " - 项目未加载"
        return status
    
    def __repr__(self) -> str:
        """返回客户端详细表示"""
        return self.__str__()


    def _build_project_from_snapshot(self, snapshot) -> Tuple[Dict[str, Any], Dict[str, Any]]:
        metadata = snapshot.metadata
        project_info: Dict[str, Any] = {
            "id": None,
            "name": metadata.name,
            "name_en": metadata.name_en,
            "description": metadata.description,
            "time_horizon": metadata.time_horizon,
            "start_year": metadata.start_year,
            "year_step": metadata.year_step,
            "end_year": metadata.end_year,
            "created_at": None,
            "updated_at": None,
        }

        categories_data: Dict[str, Any] = {}
        for category in snapshot.categories:
            category_params: Dict[str, Any] = {}
            for parameter in category.parameters:
                param_data: Dict[str, Any] = {
                    "id": None,
                    "name": parameter.name,
                    "name_en": parameter.name_en,
                    "param_type": parameter.param_type,
                    "unit": parameter.unit,
                    "description": parameter.description,
                    "is_list": parameter.is_list,
                    "is_year_related": parameter.is_year_related,
                    "list_length": parameter.list_length,
                    "created_at": None,
                    "updated_at": None,
                }

                if parameter.is_list:
                    sorted_values = sorted(
                        parameter.values,
                        key=lambda v: (float("inf") if v.index is None else v.index),
                    )
                    param_data["value"] = [value.value for value in sorted_values]
                else:
                    value_item = None
                    for snapshot_value in parameter.values:
                        if snapshot_value.value is not None:
                            value_item = snapshot_value.value
                            break
                    if value_item is None and parameter.values:
                        value_item = parameter.values[0].value
                    param_data["value"] = value_item

                category_params[parameter.name_en] = param_data

            categories_data[category.name_en] = {
                "id": None,
                "name": category.name,
                "name_en": category.name_en,
                "description": category.description,
                "created_at": None,
                "updated_at": None,
                "parameters": category_params,
            }

        return project_info, categories_data

    def _build_snapshot_payload(self, snapshot) -> Dict[str, Any]:
        """使用 JSON 策略构建快照 payload，统一在线和离线的快照格式"""
        from .backend.io_v2.strategies.json_strategy import JsonExportStrategy
        
        snapshot.ensure_export_time()
        json_strategy = JsonExportStrategy()
        return json_strategy._build_payload(snapshot)

def create_client(host: str = "localhost", port: int = 8000, project_name: str = None) -> ParameterClient:
    """
    创建参数客户端的便捷函数
    
    Args:
        host: 服务器地址
        port: 服务器端口
        project_name: 项目英文名称
        
    Returns:
        ParameterClient实例
    """
    return ParameterClient(host, port, project_name)
